package com.lwy.setter.enums;


import com.lwy.setter.types.StringOption;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 枚举缓存类
 */
@Slf4j
public class CnEnumCache {

    private static final String NAME_OPTION_SUFFIX = "Name";
    private static final String VALUE_OPTION_SUFFIX = "Value";

    /**
     * 枚举缓存，每个枚举类型对应一个键值对，key 为<font color="red">自定义的唯一键</font>，value为枚举类型<font color="red">所有枚举项对应的{@link StringOption}列表</font>，其中value的值为{@link BaseCnEnum#getValue()}
     */
    private static final Map<String, List<StringOption>> ENUM_NAME_CACHE = new ConcurrentHashMap<>();

    /**
     * 枚举缓存，每个枚举类型对应一个键值对，key 为<font color="red">自定义的唯一键</font>，value为枚举类型<font color="red">所有枚举项对应的{@link StringOption}列表</font>，其中value的值为{@link Enum#name()}
     */
    private static final Map<String, List<StringOption>> ENUM_VALUE_CACHE = new ConcurrentHashMap<>();

    /**
     * 枚举缓存，每个枚举类型对应一个键值对，key为<font color="red">自定义的唯一键</font>，value为<font color="red">枚举类型</font>
     */
    private static final Map<Class<? extends BaseCnEnum>, String> ENUM_CLZ_CACHE = new ConcurrentHashMap<>();


    //region 枚举注册接口

    /**
     * 注册枚举类到缓存中心，该方法需要手动调用，默认使用{@link Class#getSimpleName()} 作为Key注册
     *
     * @param enumClz 枚举类，必须为枚举类且实现{@link BaseCnEnum}接口或其子接口
     * @param <E>     枚举类型泛型参数
     */
    public static <E extends Enum<?> & BaseCnEnum> void register(Class<E> enumClz) {
        if (enumClz == null) {
            throw new IllegalArgumentException("enum class cannot be null");
        }
        register(enumClz.getSimpleName(), enumClz);
    }

    /**
     * <p>注册枚举类到缓存中心，该方法需要手动调用</p>
     * <p>推荐使用{@link #register(Class)} 以统一注册的key，当系统中有同名枚举时才需要调用该方法注册</p>
     *
     * @param enumKey 类型标识，忽略大小写
     * @param enumClz 要注册的枚举类型
     * @param <E>     枚举类型泛型参数
     */
    public static <E extends Enum<?> & BaseCnEnum> void register(String enumKey, Class<E> enumClz) {
        if (enumClz == null) {
            throw new IllegalArgumentException("enum class cannot be null");
        }
        if (StringUtils.isEmpty(enumKey) || !enumClz.isEnum()) {
            throw new IllegalArgumentException("Failed to register enum class [" + enumClz.getCanonicalName() + "] " + "of key [" + enumKey + "] to CnEnumCache");
        }
        // 统一转小写
        enumKey = enumKey.toLowerCase();
        // 重复注册校验
        String valOptionsKey = enumKey + VALUE_OPTION_SUFFIX;
        String nameOptionsKey = enumKey + NAME_OPTION_SUFFIX;
        if (ENUM_VALUE_CACHE.containsKey(valOptionsKey) || ENUM_CLZ_CACHE.containsKey(enumClz)) {
            // 打印告警日志
            log.warn("Duplicate register to CnEnumCache, " +
                    "key=[" + enumKey + "], enumClass=[" + enumClz.getCanonicalName() + "}]");
            return;
        }
        // 缓存数据
        try {
            // class 注册
            ENUM_CLZ_CACHE.put(enumClz, enumKey);
            BaseCnEnum[] constants = enumClz.getEnumConstants();
            if (BaseCnViewEnum.class.isAssignableFrom(enumClz)) {
                constants = Stream.of(constants)
                        .filter(e -> !((BaseCnViewEnum) e).isHidden())
                        .toArray(BaseCnViewEnum[]::new);
            }
            // 以value注册
            final List<StringOption> valueOptionItems = Arrays.stream(constants)
                    .map(CnEnumCache::ofValueOption).collect(Collectors.toList());
            ENUM_VALUE_CACHE.put(valOptionsKey, valueOptionItems);
            // 以name注册
            final List<StringOption> nameOptionItems = Arrays.stream(constants)
                    .map(CnEnumCache::ofNameOption).collect(Collectors.toList());
            ENUM_NAME_CACHE.put(nameOptionsKey, nameOptionItems);
            log.info("Register succeed,[key={}, class={}]", enumKey, enumClz.getName());
        } catch (Exception e) {
            // TODO 如何打印告警日志？？
            log.warn("Register failed,[key={}, class={}]", enumKey, enumClz.getName());
            // 异常时回退，保证3个缓存的一致性
            ENUM_CLZ_CACHE.remove(enumClz);
            ENUM_VALUE_CACHE.remove(valOptionsKey);
            ENUM_NAME_CACHE.remove(nameOptionsKey);
        }
    }

    /**
     * 注销, 该方法仅用于单元测试时清空数据，生产环境不建议使用
     *
     * @param enumClz
     * @param <E>
     */
    @Deprecated
    public static <E extends Enum<?> & BaseCnEnum> void unregister(Class<E> enumClz) {
        if (enumClz == null) {
            throw new IllegalArgumentException("enum class cannot be null");
        }
        String simpleName = enumClz.getSimpleName();
        unregister(simpleName, enumClz);
    }

    /**
     * 注销, 该方法仅用于单元测试时清空数据，生产环境不建议使用
     *
     * @param enumKey
     * @param enumClz
     * @param <E>
     */
    @Deprecated
    public static <E extends Enum<?> & BaseCnEnum> void unregister(String enumKey, Class<E> enumClz) {
        if (enumClz == null) {
            throw new IllegalArgumentException("enum class cannot be null");
        }
        if (StringUtils.isBlank(enumKey)) {
            throw new IllegalArgumentException("enumKey cannot be blank");
        }
        enumKey = enumKey.toLowerCase();
        String nameKey = genNameKey(enumKey);
        if (ENUM_NAME_CACHE.containsKey(nameKey)) {
            ENUM_NAME_CACHE.remove(nameKey);
            ENUM_VALUE_CACHE.remove(genValueKey(enumKey));
            ENUM_CLZ_CACHE.remove(enumClz);
        }
    }
    //endregion

    //region 枚举缓存查询类接口
    public static List<String> getAllEnumKeys() {
        return new ArrayList<>(ENUM_CLZ_CACHE.values());
    }

    public static List<Class<? extends BaseCnEnum>> getAllEnum() {
        return new ArrayList<>(ENUM_CLZ_CACHE.keySet());
    }

    public static List<StringOption> getNameOptions(String enumKey) {
        if (StringUtils.isBlank(enumKey)) {
            throw new IllegalArgumentException("enumKey cannot be blank");
        }
        String enumCachedKey = genNameKey(enumKey.toLowerCase());
        if (!ENUM_NAME_CACHE.containsKey(enumCachedKey)) {
            return Collections.emptyList();
        }
        return ENUM_NAME_CACHE.get(enumCachedKey);
    }

    public static <E extends Enum<?> & BaseCnEnum> List<StringOption> getNameOptions(Class<E> enumClz) {
        if (!ENUM_CLZ_CACHE.containsKey(enumClz)) {
            return Collections.emptyList();
        }
        String enumCachedKey = genNameKey(ENUM_CLZ_CACHE.get(enumClz));
        if (!ENUM_NAME_CACHE.containsKey(enumCachedKey)) {
            return Collections.emptyList();
        }
        return ENUM_NAME_CACHE.get(enumCachedKey);
    }

    public static List<StringOption> getValueOptions(String enumKey) {
        if (StringUtils.isBlank(enumKey)) {
            throw new IllegalArgumentException("enumKey cannot be blank");
        }
        String enumCachedKey = genValueKey(enumKey.toLowerCase());
        if (!ENUM_VALUE_CACHE.containsKey(enumCachedKey)) {
            return Collections.emptyList();
        }
        return ENUM_VALUE_CACHE.get(enumCachedKey);
    }

    public static <E extends Enum<?> & BaseCnEnum> List<StringOption> getValueOptions(Class<E> enumClz) {
        if (enumClz == null) {
            throw new IllegalArgumentException("enum class cannot be null");
        }
        if (!ENUM_CLZ_CACHE.containsKey(enumClz)) {
            return Collections.emptyList();
        }
        String enumCachedKey = genValueKey(ENUM_CLZ_CACHE.get(enumClz));
        if (!ENUM_VALUE_CACHE.containsKey(enumCachedKey)) {
            return Collections.emptyList();
        }
        return ENUM_VALUE_CACHE.get(enumCachedKey);
    }
    //endregion

    private static <E extends BaseCnEnum> StringOption ofValueOption(E enumItem) {
        return ofEnum(enumItem, (e) -> e.getValue().toString());
    }

    private static <E extends BaseCnEnum> StringOption ofNameOption(E enumItem) {
        return ofEnum(enumItem, (e) -> BooleanEnum.class.equals(e.getClass()) ? ((Enum<?>) e).name().toLowerCase() : ((Enum<?>) e).name());
    }

    /**
     * 用枚举项创建缓存的选项对象
     *
     * @param enumItem       枚举项
     * @param optValueGetter 选项对象的value获取函数
     * @param <E>            枚举类型泛型参数
     * @return 创建好的选项对象
     */
    private static <E extends BaseCnEnum> StringOption ofEnum(E enumItem, Function<E, String> optValueGetter) {
        // TODO 校验工具类？？？
        if (enumItem == null) {
            return null;
        }
        if (enumItem instanceof BaseCnViewEnum) {
            BaseCnViewEnum viewItem = (BaseCnViewEnum) enumItem;
            return StringOption.of(optValueGetter.apply(enumItem), viewItem.getTitle(), viewItem.getDisabled(), viewItem.getSequence());
        }
        BaseCnEnum item = (BaseCnEnum) enumItem;
        return StringOption.of(optValueGetter.apply(enumItem), item.getTitle(), null, null);
    }

    private static String genNameKey(String enumKey) {
        return enumKey + NAME_OPTION_SUFFIX;
    }

    private static String genValueKey(String enumKey) {
        return enumKey + VALUE_OPTION_SUFFIX;
    }
}
